/* ***** BEGIN LICENSE BLOCK *****
 *   Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 * 
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Save File to.
 *
 * The Initial Developer of the Original Code is
 * Atte Kemppilä.
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 * 
 * ***** END LICENSE BLOCK ***** */


function TreeDragAndDrop(tree, treeView)
{
    this.tree = tree;
    this.treeChildren = tree.getElementsByTagName("treechildren")[0];
    this.treeView = treeView;
    
    var self = this;
    this.treeChildren.addEventListener("dragstart", function(e) { self.onDragStart(e); }, false);
    this.treeChildren.addEventListener("dragenter", function(e) { self.onDragEnter(e); }, false);
    this.treeChildren.addEventListener("dragover",  function(e) { self.onDragOver(e); },  false);
    this.treeChildren.addEventListener("dragleave", function(e) { self.onDragLeave(e); }, false);
    this.treeChildren.addEventListener("drag",      function(e) { self.onDrag(e); },      false);
    this.treeChildren.addEventListener("drop",      function(e) { self.onDrop(e); },      false);
    this.treeChildren.addEventListener("dragend",   function(e) { self.onDragEnd(e); },   false);
}

TreeDragAndDrop.prototype =
{
    // source event
    onDragStart: function(e)
    {
        var dataTransfer = e.dataTransfer;
        
        var treeView = this.treeView;
        var indexes = treeView.selectedIndexes;
        var entries = treeView.selectedEntries;
        
        // don't start dragging if there's nothing to drag
        if (indexes.length == 0)
            return;
        
        var json = JSON.stringify(indexes);
        dataTransfer.setData(FLAVOR_SAVEFILETO_INDEXES, json);
        
        var isFolder = function(entry) { return (entry.type == "folder"); }
        if (entries.every(isFolder))
            dataTransfer.setData("text/plain", [entry.path for each (entry in entries)].join("\n"));
        
        if (e.ctrlKey)
            dataTransfer.effectAllowed = "copy";
        else
            dataTransfer.effectAllowed = "copyMove";
    },
    
    // target event
    onDragEnter: function(e)
    {
        var dataTransfer = e.dataTransfer;
        
        this.data = this._getData(dataTransfer);
        this.dropEffect = (this.data ? this._getDropEffect(dataTransfer) : "none");
        
        // see also view.canDrop() (which calls dnd.canDrop())
        dataTransfer.dropEffect = this.dropEffect;
        e.preventDefault();
    },
    
    // target event
    onDragOver: function(e)
    {
        var dataTransfer = e.dataTransfer;
        
        // see also view.canDrop() (which calls dnd.canDrop())
        dataTransfer.dropEffect = this.dropEffect;
        e.preventDefault();
    },
    
    // target event
    onDragLeave: function(e)
    {
        // do nothing
    },
    
    // source event
    onDrag: function(e)
    {
        // do nothing
    },
    
    // target event
    onDrop: function(e)
    {
        // actual drop is handled in view.drop() (which calls dnd.drop())
        this.dropEffect = e.dataTransfer.dropEffect;
    },
    
    // source event
    onDragEnd: function(e)
    {
        // do nothing
    },
    
    // nsITreeView.canDrop()
    canDrop: function(row, orientation)
    {
        return (this.dropEffect && (this.dropEffect != "none"));
    },
    
    // nsITreeView.drop() (see dnd.onDrop())
    drop: function(row, orientation)
    {
        row = row + Math.max(0, orientation);
        
        var data = this.data;
        var dropEffect = this.dropEffect;
        
        // if drag ends without drop, delete is never done
        delete this.data;
        delete this.dropEffect;
        
        switch (dropEffect)
        {
            case "copy": this._copy(data, row); break;
            case "move": this._move(data, row); break;
            default: throw new Error("unexpected drop: " + dropEffect);
        }
    },
    
    _copy: function(data, row)
    {
        // drag across views isn't implemented -> assuming: source == target
        
        var treeView = this.treeView;
        
        var entries;
        if (data.entries)
            entries = data.entries;
        else
            entries = [treeView.entries[index] for each (index in data.indexes)];
        
        treeView.addEntries(row, entries, true);
    },
    
    _move: function(data, row)
    {
        // drag across views isn't implemented -> assuming: source == target
        
        var treeView = this.treeView;
        var indexes = data.indexes;
        treeView.moveEntriesTo(indexes, row);
    },
    
     _getData: function(dataTransfer)
    {
        var types = dataTransfer.types;
        
        if (types.contains(FLAVOR_SAVEFILETO_INDEXES))
        {
            var json = dataTransfer.getData(FLAVOR_SAVEFILETO_INDEXES);
            var indexes = JSON.parse(json);
            return {indexes: indexes};
        }
        
        var entry, file, url, path;
        
        if (types.contains("text/plain"))
            path = url = dataTransfer.getData("text/plain").trim();
        
        if (path)
        {
            try
            {
                var f = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
                f.initWithPath(path);
                file = f;
            }
            catch (e) { /* ignore */ }
        }
        
        if (url && (! file))
        {
            try
            {
                file = ioSvc.newURI(url, null, null).QueryInterface(Ci.nsIFileURL).file;
            }
            catch (e) { /* ignore */ }
        }
        
        if (file && file.exists() && file.isDirectory())
            entry = {type: "folder", label: file.leafName, path: file.path, menu: true};
        
        return (entry ? {entries: [entry]} : null);
    },
    
    _getDropEffect: function(dataTransfer)
    {
        var allowed = dataTransfer.effectAllowed
        
        var isMove = (["all", "move", "copyMove", "linkMove"].indexOf(allowed) != -1);
        var hasEntries = dataTransfer.types.contains(FLAVOR_SAVEFILETO_INDEXES);
        
        if (isMove && hasEntries)
            return "move";
        
        var isCopy = (["all", "copy", "copyMove", "copyLink"].indexOf(allowed) != -1);
        var hasText = dataTransfer.types.contains("text/plain");
        
        if (isCopy && (hasEntries || hasText))
            return "copy";
        
        return "none";
    },
}
